package com.heo.music.player.auth;

import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import com.heo.music.player.constant.AuthConst;
import com.heo.music.player.dao.UserInfoDao;
import com.heo.music.player.entity.UserInfo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.http.HttpStatus;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;


/**
 * @author 星天无极
 */
@Component
@Slf4j
@RequiredArgsConstructor
public class AuthInterceptor implements HandlerInterceptor {

    private final UserInfoDao userInfoDao;

    private static String calculateSignature(String secretKey, HttpServletRequest request) throws NoSuchAlgorithmException, InvalidKeyException {
        return calculateSignature(secretKey, request.getMethod(), request.getRequestURI());
    }

    private static String calculateSignature(String secretKey, String method, String resourcePath) throws NoSuchAlgorithmException, InvalidKeyException {
        String stringToSign = method + "\n" + resourcePath;
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        mac.init(secretKeySpec);
        byte[] signatureBytes = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
        return Base64.encodeBase64String(signatureBytes);
    }

    @Override
    public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) throws NoSuchAlgorithmException, InvalidKeyException {

        // 如果不是映射到方法上则直接通过
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();

        if (method.isAnnotationPresent(NeedLogin.class)) {
            UserInfo userInfo;
            String authorization = request.getHeader(AuthConst.AUTHORIZATION);
            if (authorization == null || authorization.isEmpty()) {
                throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "用户未登录");
            }
            if (authorization.startsWith(AuthConst.BEARER)) {
                userInfo = authByBearer(authorization);
            } else {
                throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "认证无效");
            }

            UserInfoContextHolder.setUserInfo(userInfo);
        }
        return true;
    }

    private UserInfo authByBearer(String authorization) {
        String token = authorization.substring(AuthConst.BEARER.length());


        //判断token是否无效或者过期
        JWT jwt;
        try {
            jwt = JWTUtil.parseToken(token);

        } catch (Exception e) {
            log.error("token无效或token以过期, token:{}", token);
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "token无效");
        }

        //取出用户ID进行判断
        Integer userId = jwt.getPayload().getClaimsJson().getInt(AuthConst.USER_ID);
        if (userId == null) {
            log.error("token无效, token:{}", token);
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "token无效");
        }

        return userInfoDao.findById(userId).orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "当前用户不存在"));
    }

    @Override
    public void afterCompletion(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler, Exception ex) {
        UserInfoContextHolder.clear();
    }

}
